#
#@author Devdoer<devdoer@gmail.com>
#



import urlparse,threading,socket,errno,threading,urllib
from anole import AnoleLog,AnoleError,NetworkError
from anole.utils import httplib 
from anole.utils.url import Url,InvalidUrl
from cStringIO import StringIO
import base64,time
from anole.core.frontier import EGetUrl
from anole.core.dns import DnsError
	

class FetchError(NetworkError):
	pass
class EDns(FetchError):
	pass
class EConnTimeout(FetchError):
	pass
class EIOTimeout(FetchError):
	pass
class EConnReset(FetchError):
	pass
class EConnRefused(FetchError):
	pass
class ERoute(FetchError):
	pass
	
class HTTPFetchRequest(object):
	__slots__=['uObj','strUrl','timeout','onProcess']

	def __init__(self,url=None,timeout=None):
		#None means no timeout & blocking
		self.uObj=url
		self.timeout=timeout
class RIS:
#stream  that can be re-read many times
#read data from file into memory for effeciency
#tobe re-written
    def __init__(self,file):
        self._file=file
        self._buf=StringIO()
        self._bufInit=False
	self._log=AnoleLog().log
        #self._idx=0

    def init_buf(self):
    	if self._bufInit:
    		return
    	self.reset()
    	try:
    		data=self._file.read()
    		self._file.close()
    	except socket.error,e:
    		if isinstance(e,socket.timeout):
				raise EIOTimeout(e)
    		if e[0]==errno.ECONNRESET:
    			raise EConnReset(e)
    		raise	
    	self._buf.write(data)
    	self._bufInit=True
    	
    	self.reset()
    
    def read(self):
    
        if not self._bufInit:
        	self.init_buf()
            
           
        return self._buf.read()
    def reset(self):
     #set to re-read from start   
        self._buf.seek(0)
    def close(self):
    	self._file=None
            
        self._buf.close()
def RISWrap(file):
#return a ris  that the ris's buffer is not initialized
    if isinstance(file,RIS):
        file.reset()
        return file
    else :
        ris=RIS(file)
        #init the buffer
        #ris.init_buf()
        return ris        
class HttpFetcher:
	class _MyHTTP(httplib.HTTP):
		#_http_vsn=11
		#_http_vsn_str='HTTP/1.1'
		pass
	class _ReplyWrap(object):
		__slots__=['_head','_file']
		def __init__(self,reply):
			self._head=reply.getreply()
			file=reply.getfile()
			if file:
				self._file=RISWrap(file)
			else:
				self._file=None
		def get_head(self):
			try:
				h=self._head[2].dict.copy()
				h.update({'code':self._head[0]})
			except:
				h={'code':-1}
			return h
		def get_file(self):
			return self._file
	
	def __init__(self):
		self._log=AnoleLog().log
	def _create_request(self,uObj,timeout=100):
		r=HTTPFetchRequest()
		r.timeout=timeout
		r.strUrl=uObj.strUrl
		r.uObj=uObj
		r.onProcess=True
			
		return r
	def set_usp(self,usp):
		self._usp=usp
	def set_handler(self,h):
		self._handler=h
	def set_dns(self,dns):
		self._dns=dns
	def set_cocurrency(self,co):
		self._cocurrency=[]
		for i in range(co):
			t=threading.Thread(target=self._sub_run)
			t.setDaemon(True)
			self._cocurrency.append(t)
	def run(self):
		for c in self._cocurrency:
			c.start()
		for c in self._cocurrency:
			c.join()
	def _sub_run(self):
		usp=self._usp
		handler=self._handler
		dns=self._dns
		while 1:
			self._log('try get a new link...',AnoleLog.LEVEL_INFO)
			try:
				U=usp.get_url()
			except EGetUrl,e:
				if e[0]==EGetUrl.WAIT:
					self._log('no url available,sleep for a while',AnoleLog.LEVEL_DEBUG)
					time.sleep(e[1])
					continue
				elif e[0]==EGetUrl.RETRY:
					self._log('retry get url',AnoleLog.LEVEL_DEBUG)
					continue
			except AnoleError,e:
				self._log(str(e),AnoleLog.LEVEL_ERROR)
				break
			strUrl=U.strUrl
			self._log('get a link: %s'%(urllib.unquote(strUrl)),AnoleLog.LEVEL_INFO)
			try:
				url=Url(strUrl)
			except AnoleError,e:
				self._log(repr(e)+str(e),AnoleLog.LEVEL_WARNING)
				continue
			strHost=url.host()
			reply=None
			req=None
			req=self._create_request(U)	
			try:	
				ip,port,user=U.proxy
			except (AttributeError,TypeError):
				useProxy=False	
				try:
					ip=U.ip
				except AttributeError:	
					try:
						ip=dns.resolve_host(strHost)
					except DnsError,e:
						handler.on_anole_error(EDns(strHost,str(e)),req)
						continue
					#except :
					#	raise
					#	handler.on_anole_error(EDns(strHost),req)
					#	continue
				port=url.port()
				strFileName=url.path_to_file()
			else:
				useProxy=True
				strFileName=strUrl
				addProxyInfo='Basic '+base64.b64encode(user)
			try:
			
				h=self._MyHTTP(ip,port)
				self._log('fetch: %s -->start'%(strUrl), \
					AnoleLog.LEVEL_INFO)
				if not useProxy: 
					self._log('ip is %s, port is %d'%(ip,port),AnoleLog.LEVEL_DEBUG)
				else:	
					self._log('use proxy: %s,%d'%(ip,port),AnoleLog.LEVEL_DEBUG)
				h.putrequest('GET',strFileName)
				h.putheader('Host',strHost)
				h.putheader('User-Agent',r'Mozilla/4.0 (compatible; MSIE 6.0; )')
				if useProxy:
					h.putheader('Proxy-Authorization',addProxyInfo)
				#close keep-alive,for it will block our call
				#h.putheader('Connection','close')
				h.endheaders()
				h._conn.sock.settimeout(req.timeout)
				reply=self._ReplyWrap(h)
				self._log('fetch: %s -->succeed'%(strUrl),AnoleLog.LEVEL_INFO)

				head=reply.get_head()
				if handler.on_http_head_ok(head,req):
					handler.on_http_reply_ok(reply,req)

				#close the connnection
				file=reply.get_file()
				if file:
					file.close()
					
			
			except socket.error,e:
				e=self._process_nw_error(e,strHost)
				handler.on_anole_error(e,req)
			except AnoleError,e:	
				handler.on_anole_error(e,req)
			#h.close()-->HTTPConnection.close()-->HTTPResponse.close()-->
			#-->HTTPResponse.fp.close()==self.reply.file.close()-->socket._FileObject._sock=None

	def _process_nw_error(self,e,strHost):
		#connect call may raise socket.error->ETIMEDOUT,for server's
		# 		accept buffer is full or serve is down
		#after settimeout,our call may raise socket.timeout
		if isinstance(e,socket.timeout) :
			return EIOTimeout(strHost)
		if e[0]==errno.ETIMEDOUT:
			return EConnTimeout(strHost)
		if e[0]==errno.ECONNREFUSED:
			return EConnRefused(strHost)
		if e[0]==errno.ECONNRESET:
			return EConnReset(strHost)
		if e[0]==113:
			return ERoute(strHost)
		raise


def main():
	pass

if __name__=='__main__':
	main()
